package last.fm;

/***
 * @author Ragnvald Fuglestein
 */

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

import baseStructure.Log;

import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.google.common.io.ByteStreams;

//TODO: Rewrite

public class LastFm {
	private static LastFm instance = null;
	private static JsonFactory jsonfactory = null;
	
	private static final String API = "http://ws.audioscrobbler.com/2.0/?method=";
	private static final String KEY = "f63d552914a922094d90da796680f636";
	
	public static LastFm getInstance() {
		if (instance == null) {
			instance = new LastFm();
			jsonfactory = new JsonFactory();
		}
		return instance;
	}
	
	private LastFm() {}
	
	public InputStream request(String method, String[] params) throws IOException {
		InputStream stream = getCached(method, params);
		if (stream == null) {
			String urlstr;
			urlstr = LastFm.API + method + "&api_key=" + LastFm.KEY + "&format=json&raw=true";
			
			if (params != null) {
				for (String s : params) {
					urlstr += "&" + s;
				}
			}
			
			HttpURLConnection conn;
			try {
				URL url = new URL(urlstr);
				conn = (HttpURLConnection) url.openConnection();
				if (conn.getResponseCode() != 200) {
					throw new IOException(conn.getResponseMessage());
				}
			} catch (MalformedURLException e) {
				throw new IOException(e);
			}
			LastFmJobWorker.madeRequest(); // Inform worker thread about the request
			if (writeCache(method, params, conn.getInputStream())) {
				Log.d("Successfully cached request");
				conn.disconnect();
				return request(method, params);
			} else {
				Log.d("Cache failed");
				throw new IOException("Unknown error in last.fm request");
			}
		} else {
			Log.d("Using cached request");
			return new BufferedInputStream(stream);
		}
	}

	private static InputStream getCached(String method, String[] params) {
		InputStream stream;
		int hashCode = method.hashCode();
		for (String s : params) {
			hashCode += s.hashCode();
		}
		File file = new File(System.getProperty("user.home") + File.separator + "artistlib" + File.separator + "cache" + File.separator + hashCode + method + ".json.gz");
		try {
			stream = new FileInputStream(file);
		} catch (FileNotFoundException e) {
			return null;
		}
		try {
			stream = new BufferedInputStream(new GZIPInputStream(stream));
		} catch (IOException e) {
			try {
				stream.close();
			} catch (IOException e1) { }
			return null;
		}
		return stream;
	}

	private static boolean writeCache(String method, String[] params, InputStream stream) {
		OutputStream writeable = null;
		int hashCode = method.hashCode();
		for (String s : params) hashCode += s.hashCode();
		File file = new File(System.getProperty("user.home") + File.separator + "artistlib" + File.separator + "cache" + File.separator + hashCode + method + ".json.gz");
		try {
			File parent = file.getParentFile();
			if (!parent.exists()) {
				parent.mkdirs();
			}
			writeable = new BufferedOutputStream(new GZIPOutputStream(new FileOutputStream(file)));
			ByteStreams.copy(stream, writeable);
			writeable.flush();
			writeable.close();
		} catch (IOException e) {
			e.printStackTrace();
			try { stream.close(); } catch (IOException e1) { }
			if (writeable != null) { try { writeable.close(); } catch (IOException e1) { } }
			if (file.exists()) file.delete();
			return false;
		}

		return true;
	}
	
	private String parseString(JsonParser jp) throws IOException {
		jp.nextToken();
		return jp.getText();
	}
	private int parseInt(JsonParser jp) throws IOException {
		jp.nextToken();
		try {
			return Integer.parseInt(jp.getText());
		} catch (NumberFormatException e) { return -1; }
	}

	@SuppressWarnings("deprecation")
	public void parseTracks(InputStream stream, List<LastFmTrackAddon> list) throws IOException {
		JsonParser jp = null;
		try {
			jp = jsonfactory.createJsonParser(stream);
			jp.nextToken();
			JsonToken token;
			while ((token = jp.nextToken()) != JsonToken.END_OBJECT) {
				if ("track".equals(jp.getCurrentName()) && token == JsonToken.START_ARRAY) {
					while ((token = jp.nextToken()) != JsonToken.END_ARRAY) {
						if (token == JsonToken.START_OBJECT) {
							LastFmTrackAddon track = parseTrack(jp);
							list.add(track);
						} else {
							//Log.d("IGNORE 2: " + jp.getCurrentName() +" : " + token + " : " + jp.getText());
						}
					}
				}
				else {
					//Log.d("IGNORE 1: " + jp.getCurrentName() +" : " + token + " : " + jp.getText());
				}
			}
		} catch (IOException e) {
			if (jp != null)
				try {
					jp.close();
				} catch (IOException e1) { }
			try {
				stream.close();
			} catch (IOException e1) { }
			
			throw e;
		}
	}
	
	private LastFmTrackAddon parseTrack(JsonParser jp) throws IOException {
		JsonToken token;
		LastFmTrackAddon track = new LastFmTrackAddon();
		
		while ((token = jp.nextToken()) != JsonToken.END_OBJECT) {
			String fieldname = jp.getCurrentName();
			if (fieldname.equals("name")) track.setTrackName(parseString(jp));
			else if (fieldname.equals("duration")) track.setDuration(parseInt(jp));
			//else if (fieldname.equals("playcount")) track.addPlayCount(lastfmuser, parseInt(jp));
			else if (fieldname.equals("mbid")) track.setTrackMBID(parseString(jp));
			else if (fieldname.equals("url")) track.setTrackURL(parseString(jp));
			else if (fieldname.equals("streamable")) {
				token = jp.nextToken();
				while ((token = jp.nextToken()) != JsonToken.END_OBJECT) {
					//Log.d("IGNORE!!: " + jp.getCurrentName() +" : " + token + " : " + jp.getText());
				}
			}
			else if (fieldname.equals("date")) {
				while ((token = jp.nextToken()) != JsonToken.END_OBJECT) {
					//Log.d("IGNORE!!: " + jp.getCurrentName() +" : " + token + " : " + jp.getText());
				}
			}
			else if (fieldname.equals("image")) {
				token = jp.nextToken();
				while ((token = jp.nextToken()) != JsonToken.END_ARRAY) {
					//Log.d("IGNORE!!: " + jp.getCurrentName() +" : " + token + " : " + jp.getText());
				}
			}
			else if (fieldname.equals("artist")) {
				token = jp.nextToken();
				while ((token = jp.nextToken()) != JsonToken.END_OBJECT)
				{
					String a = jp.getCurrentName();
					if (a.equals("name")) {
						token = jp.nextToken();
						track.setArtistName(jp.getText());
					}
					else if (a.equals("mbid")) {
						token = jp.nextToken();
						track.setArtistMBID(jp.getText());
					}
					else if (a.equals("url")) {
						token = jp.nextToken();
						track.setArtistURL(jp.getText());
					}
					else {
						//Log.d("IGNORE 4: " + jp.getCurrentName() +" : " + token + " : " + jp.getText());
					}
				}
			}
			else if (fieldname.equals("rank")) {
				token = jp.nextToken();
				try {
					track.setRank(Integer.parseInt(jp.getText()));
				} catch (NumberFormatException e) { }
			}
			else if (fieldname.equals("downloadurl")) {
				token = jp.nextToken();
				track.setDownloadURL(jp.getText());
			}
			else {
				//Log.d("IGNORE 3: " + jp.getCurrentName() +" : " + token + " : " + jp.getText());
			}
		}
		return track;
	}
}
